﻿#define CacheToStringResults
//#define FastEquals

using System;
using System.Collections;
using System.Collections.Generic;
using linq = Microsoft.Linq.Expressions;
using System.Linq;
using Bling.Util;

namespace Bling.DSL {
  public partial interface IAssign {
    bool Accept<T>(Expr<T> LHS, Expr<T> RHS);
    IAssign Copy();
  }
  public partial interface ExprVisitor<R> {
    R Visit<T>(Expr<T> value);
  }
  public partial interface IExpr {
    Type TypeOfT { get; }
    object BaseEval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    R Visit<R>(ExprVisitor<R> v);
    bool Solve(IAssign assign, IExpr value);
  }
  public partial interface IExpr<T> : IExpr { }
  public partial interface IHasContainer : IExpr {
    Expr BaseContainer { get; }
  }

  public partial interface IHasContainer<S, T> : IExpr<T>, IHasContainer {
    Expr<S> Container { get; }
  }
  public abstract partial class Expr : IExpr {
    public abstract Type TypeOfT { get; }
    public abstract object BaseEval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    public abstract R Visit<R>(ExprVisitor<R> v);
    public abstract bool Solve(IAssign assign, IExpr value);

    public virtual int IdentityHashCode { get { return base.GetHashCode(); } }
    public virtual bool IsConstant { get { return false; } }
    public virtual bool IsObviousConstant { get { return false; } }
    public virtual Expr Identity { get { return this; } }
    public virtual bool HasCurrentValue { get { return true; } }
  }
  public partial interface IBindableExpr : IExpr {
    bool BindWith(Expr value);
  }

  public partial interface IBindableExpr<T> : IExpr<T>, IBindableExpr {
    bool BindWith(Expr<T> value);
    bool DelayBindWith(Expr<T> value, List<Action> DoBind);
    bool TwoWayBindWith(Expr<T> value);
  }
  public partial interface ICanAssignNowExpr<T> : IExpr<T> {
    TDelegate SetNow<TDelegate>(Expr<T> value, params IParameterExpr[] param);
    bool SetNow(T value);
  }

  

  public partial class BindAssign : IAssign {
    private BindAssign() { }
    public static BindAssign Instance = new BindAssign();
    public Helper Top = new Helper.Default();
    public void Activate(Helper Helper, Action A) {
      (Helper.Previous == null).Assert();
      (Top != null).Assert();
      Helper.Previous = Top;
      Top = Helper;
      A();
      Top = Helper.Previous;
      Helper.Previous = null;
    }

    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      return Top.AcceptBind(LHS, RHS);
    }
    public IAssign Copy() { return this; }
    public abstract partial class Helper {
      internal Helper Previous;
      public abstract bool AcceptBind<T>(Expr<T> LHS, Expr<T> RHS);

      public class Default : Helper {
        public override bool AcceptBind<T>(Expr<T> LHS, Expr<T> RHS) {
          if (LHS is IBindableExpr<T>) {
            if (((IBindableExpr<T>)LHS).BindWith(RHS)) return true;
          }

          return false;
        }
      }
    }
  }
  public partial class TwoWayAssign : IAssign {
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is IBindableExpr<T>)
        return ((IBindableExpr<T>)LHS).TwoWayBindWith(RHS);
      return false;
    }
    public IAssign Copy() { return this; }
  }
  public interface INowAssign : IAssign {

  }
  public partial class NowAssign<TDelegate> : INowAssign {
    public readonly IParameterExpr[] Arguments;
    public NowAssign(params IParameterExpr[] Arguments) { this.Arguments = Arguments; }
    public readonly List<TDelegate> Results = new List<TDelegate>();
    public IAssign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is ICanAssignNowExpr<T>) {
        var now = ((ICanAssignNowExpr<T>)LHS).SetNow<TDelegate>(RHS, Arguments);
        if (now == null) return false;
        Results.Add(now);
        return true;
      } else if (LHS is NoAssignExpr<T>) return true;
      else return false;
    }
  }
  public partial interface IParameterExpr : IExpr {
    string Name { get; set; }
  }
  public partial class ParameterExpr<T> : Expr<T>, IParameterExpr {
    public string Name { get; set; }
    private readonly object Key;
    public ParameterExpr() {
      Key = new object();
      Name = "P" + (Key.GetHashCode() % 100);
    }
    public ParameterExpr(object Key) {
      this.Key = Key;
      Name = Key.ToString();
    }
    protected override string ToString1() { return Name; }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is IParameterEval<EVAL>) {
        var ret = ((IParameterEval<EVAL>)txt).Parameter(this);
        if (ret != null) return ret;
      }
      throw new NotImplementedException();
    }
    protected override int GetHashCode0() {
      return Key.GetHashCode();
    }
    protected override bool Equals0(Expr<T> obj) {
      if (obj is ParameterExpr<T>) return Key.Equals(((ParameterExpr<T>)obj).Key);
      return base.Equals0(obj);
    }
  }
  public partial interface IParameterEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> Parameter<T>(ParameterExpr<T> value);
  }
  public partial class LoopIndexExpr : ParameterExpr<int> {
    public readonly Expr<int> Count;
    public LoopIndexExpr(object key, Expr<int> Count) : base(key) { this.Count = Count; }
    public LoopIndexExpr(Expr<int> Count) : base() { this.Count = Count; }
  }
  // XXX: move to Vertices
  public enum DynamicMode {
    Static = 0, Dynamic = 1,
  }

  public interface IDynamicEval<EVAL> : IEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> Dynamic<T>(DynamicMode Mode);
    Eval<EVAL>.Info<T> Dynamic<T>(Expr Other);
  }
  public interface IExpressionEval<EVAL, EXPRESSION> : IEval<EVAL> where EVAL : Eval<EVAL> {
    EXPRESSION Extract<T>(Eval<EVAL>.Info<T> Info);
    IParameterExpr[] ExpressionsUsed { get; }
  }

  public interface ICanAssignExpr : IExpr { }
  public interface ICanAssignExpr<EXPRESSION> : ICanAssignExpr {
    EXPRESSION Assign<EVAL>(EXPRESSION RHS0, IExpressionEval<EVAL, EXPRESSION> txt) where EVAL : Eval<EVAL>;
  }
  public interface ICanAssignExprT<T> : ICanAssignExpr, IExpr<T> { }
  public interface ICanAssignExpr<T, EXPRESSION> : ICanAssignExprT<T>, ICanAssignExpr<EXPRESSION> { }
  public interface IUseInUI : IExpr {}
  public interface IUseInUI<T> : IUseInUI, IExpr<T> { }

  public interface INoAssignExpr : IExpr { }
  public class NoAssignExpr<T> : Expr<T>, INoAssignExpr {
    private readonly Expr<T> Underlying;
    public NoAssignExpr(Expr<T> Underlying) {
      this.Underlying = Underlying;
    }
    protected override int GetHashCode0() { return Underlying.GetHashCode(); }
    protected override bool Equals0(Expr<T> obj) {
      return obj is NoAssignExpr<T> && ((NoAssignExpr<T>)obj).Underlying.Equals(Underlying);
    }
    protected override string ToString1() { return Underlying.ToString(); }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is TranslateEval<EVAL>) return ((TranslateEval<EVAL>)txt).NoTranslation(new NoAssignExpr<T>(
        ((TranslateEval<EVAL>.MyInfo<T>)txt.DoEval(Underlying)).Value
        ));
      return txt.DoEval(Underlying);
    }
  }

  public partial interface ILoopEval<EVAL, EXPRESSION> : IExpressionEval<EVAL, EXPRESSION> where EVAL : Eval<EVAL> {
    EXPRESSION DoLoop(LoopIndexExpr i, Func<EVAL, EXPRESSION> a);
    EXPRESSION MakeAssign<T>(Expr<T> LHS, Expr<T> RHS);
    EXPRESSION MakeAssign(Expr LHS, Expr RHS);
    EXPRESSION DoIf(EXPRESSION Test, Func<EVAL, EXPRESSION> IfTrue, Func<EVAL, EXPRESSION> IfFalse);
    EXPRESSION Exception { get; }
    EXPRESSION Block(IList<EXPRESSION> Expressions);
    EXPRESSION End(EXPRESSION e);
  }

  public static partial class DSLExtensions {
    public static EXPRESSION DoLoopAssign<T, EVAL, EXPRESSION>(ILoopEval<EVAL, EXPRESSION> Eval, Expr<T> LHS, Expr<T> RHS, params LoopIndexExpr[] Indices)
      where EVAL : Eval<EVAL> {
      if (Indices.Length == 1) {
        return Eval.DoLoop(Indices[0], (txt) => ((ILoopEval<EVAL, EXPRESSION>)txt).MakeAssign<T>(LHS, RHS));
      } else {
        var NewIndices = new LoopIndexExpr[Indices.Length - 1];
        for (int i = 1; i < Indices.Length; i++) NewIndices[i - 1] = Indices[i];
        return Eval.DoLoop(Indices[0], (txt) =>
          DoLoopAssign((ILoopEval<EVAL, EXPRESSION>)txt, LHS, RHS, NewIndices));
      }

    }
  }
  public abstract partial class TranslateEval<EVAL> : Eval<EVAL>, IParameterEval<EVAL> where EVAL : Eval<EVAL> {
    public interface MyInfo : Info {
      Expr BaseValue { get; }
    }
    public Eval<EVAL>.Info<T> Block<T>(T[] Values, Eval<EVAL>.Info<int> idx) { throw new NotSupportedException(); }


    public class MyInfo<T> : Info<T>, MyInfo {
      public Expr<T> Value;
      public Expr BaseValue { get { return Value; } }
    }
    private MyInfo<T> DoEvalX<T>(Expr<T> Expr) { return (MyInfo<T>)((Eval<EVAL>)this).DoEval(Expr); }
    private MyInfo DoEvalX(Expr Expr) { return (MyInfo)((Eval<EVAL>)this).DoEval(Expr); }

    public override Info<T> DoEval<T>(Expr<T> Expr) {
      return base.DoEval(RealTranslate(Expr));
    }
    protected Expr<T> EvalWithoutTranslate<T>(Expr<T> Expr) {
      return ((MyInfo<T>)base.DoEval(Expr)).Value;
    }
    private class MyVisitor : ExprVisitor<Expr> {
      internal TranslateEval<EVAL> Outer;
      public Expr Visit<T>(Expr<T> e) {
        return Outer.EvalWithoutTranslate(e);
      }
    }

    protected Expr EvalWithoutTranslate(Expr Expr) {
      return Expr.Visit(new MyVisitor() { Outer = this });
    }

    public Expr<T> Translate<T>(Expr<T> value) { return DoEvalX(value).Value; }
    public BRAND Translate<T, BRAND>(Core.Brand<T, BRAND> value) where BRAND : Core.Brand<T, BRAND> {
      return Core.Brand<T, BRAND>.ToBrand(Translate(value.Provider));
    }

    public Expr Translate(Expr value) { return DoEvalX(value).BaseValue; }
    protected virtual Expr<T> RealTranslate<T>(Expr<T> value) { return value; }
    public Info<T> NoTranslation<T>(Expr<T> value) {
      return new MyInfo<T>() { Value = value };
    }
    public override Info<T> Constant<T>(Constant<T> constant) { return NoTranslation(constant); }
    public Info<T> Parameter<T>(ParameterExpr<T> value) { return NoTranslation(value); }

  }
  public class ReplaceEval : TranslateEval<ReplaceEval> {
    public readonly Dictionary<IExpr, IExpr> Replace = new Dictionary<IExpr, IExpr>();
    public IExpr this[IExpr From] { set { Replace[From] = value; } }
    protected override Expr<T> RealTranslate<T>(Expr<T> value) {
      if (Replace.ContainsKey(value)) return (Expr<T>)Replace[value];
      else return base.RealTranslate<T>(value);
    }
  }

  public abstract partial class Expr<T> : Expr, IExpr<T> {
    // public T CurrentValue { get; }
    public override R Visit<R>(ExprVisitor<R> v) {
      return v.Visit<T>(this);
    }

    public static implicit operator Expr<T>(T t) { return new Constant<T>(t); }

#if CacheToStringResults
    private string ToString0;
#endif

    public sealed override string ToString() {
#if CacheToStringResults
      if (ToString0 != null) return ToString0;
#endif
      var ret = ToString1();
#if CacheToStringResults
      ToString0 = ret;
#endif
      return ret;
    }

    private int? hc;
    public sealed override int GetHashCode() {
      if (hc == null) hc = GetHashCode0();
      return (int)hc;
    }
    public sealed override bool Equals(object obj) {
      if (object.ReferenceEquals(this, obj)) return true;
      if (!(obj is Expr<T>)) return false;
      if (GetHashCode() != obj.GetHashCode()) return false;
#if FastEquals
      return ToString().Equals(obj.ToString());
#else
      return (Equals0((Expr<T>)obj));
#endif
    }
    protected virtual bool Equals0(Expr<T> e) {
      return base.Equals(e);
    }


    protected virtual int GetHashCode0() { return base.GetHashCode(); }
    protected virtual string ToString1() { return base.ToString(); }

    public override Type TypeOfT {
      get { return typeof(T); }
    }
    public virtual bool Solve(IAssign P, Expr<T> RHS) {
      var ret = P.Accept(this, RHS);
      return ret;
    }
    public override bool Solve(IAssign assign, IExpr value) {
      return Solve(assign, (Expr<T>)value);
    }
    protected abstract Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    internal Eval<EVAL>.Info<T> Eval000<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL> { return Eval(txt); }

    public override object BaseEval<EVAL>(Eval<EVAL> txt) {
      return Eval(txt);
    }
    public Expr<T> Bind {
      set {
        if (!Solve(BindAssign.Instance, value)) {
          Solve(BindAssign.Instance, value);
          throw new NotSupportedException();
        }
      }
    }
    public Expr<T> TwoWay {
      set {
        if (!Solve(new TwoWayAssign(), value))
          throw new NotSupportedException();
      }
    }
  }
  public interface IProxyExpr : IExpr {
    Expr BaseUnderlying { get; }
  }
  public abstract partial class ProxyExpr<S, T> : Expr<T>, IProxyExpr {
    public readonly Expr<S> Underlying;
    public ProxyExpr(Expr<S> Underlying) {
      this.Underlying = Underlying;
    }
    protected override int GetHashCode0() {
      return Underlying.GetHashCode();
    }
    protected override bool Equals0(Expr<T> obj) {
      return obj is ProxyExpr<S,T> && Underlying.Equals(((ProxyExpr<S,T>)obj).Underlying);
    }
    protected override string ToString1() {
      return Underlying.ToString() + ".As[" + typeof(T).Name + "]";
    }
    public Expr BaseUnderlying { get { return Underlying; } }
    public override bool IsConstant {
      get { return false; }
    }
  }
  public abstract partial class ProxyExpr<T> : Expr<T>, IProxyExpr {
    public readonly Expr<T> Underlying;
    public ProxyExpr(Expr<T> Underlying) {
      this.Underlying = Underlying;
    }
    public override Expr Cheap {
      get {
        return base.Cheap;
        //return Underlying.Cheap;
      }
    }
    protected override int GetHashCode0() {
      return Underlying.GetHashCode();
    }
    protected override bool Equals0(Expr<T> obj) {
      return obj is ProxyExpr<T> && Underlying.Equals(((ProxyExpr<T>)obj).Underlying);
    }
    protected override string ToString1() {
      return Underlying.ToString();
    }
    public Expr BaseUnderlying { get { return Underlying; } }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is TranslateEval<EVAL>) return
        ((TranslateEval<EVAL>)txt).NoTranslation(
          Make(((TranslateEval<EVAL>)txt).Translate(Underlying))
        );
      return txt.DoEval(Underlying);
    }
    public Expr<S> Make0<S>(Func<Expr<T>, Expr<S>> f) {
      return Make<S>(f(Underlying));
    }
    protected abstract Expr<S> Make<S>(Expr<S> u);
    public override bool Solve(IAssign P, Expr<T> other) {
      return base.Solve(P, other) || Underlying.Solve(P, other);
    }
    public override bool IsConstant {
      get { return false; }
    }
  }
  public partial interface IEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> DoEval<T>(Expr<T> Expr);
  }
  public abstract partial class Eval {
    public interface Info { }
    public interface Info<T> : Info { }
  }

  public abstract partial class Eval<EVAL> : Eval, IEval<EVAL> where EVAL : Eval<EVAL> {
    public new interface Info : Eval.Info { }
    public new interface Info<T> : Eval.Info<T>, Info { }
    public abstract Info<T> Constant<T>(Constant<T> constant);
    public static implicit operator EVAL(Eval<EVAL> eval) { return (EVAL)eval; }
    public virtual Info<T> DoEval<T>(Expr<T> Expr) { return Expr.Eval000(this); }

    private class EvalVisitor : ExprVisitor<Info> {
      public EVAL Outer;
      public Info Visit<T>(Expr<T> value) {
        return Outer.DoEval(value);
      }
    }
    public Info DoEval(Expr Expr) {
      return Expr.Visit(new EvalVisitor() { Outer = this });
    }
  }
  public interface IConstant : IExpr {
    object BaseValue { get; }
  }
  public partial class Constant<T> : Expr<T>, IConstant {
    public readonly T Value;
    public Constant(T value) {

      if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Vecs.IVec<,>)) {
        (value != null).Assert();
      }
      if (value == null) {
        //Console.WriteLine("");
      }
      this.Value = value;
    }
    public Constant() : this(default(T)) { }
    protected override string ToString1() {
      return Value.ToString();
    }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      return txt.Constant(this);
    }
    protected override int GetHashCode0() {
      return Value == null ? 0 : Value.GetHashCode();
    }
    protected override bool Equals0(Expr<T> obj) {
      if (Value == null) return obj == null;
      else if (obj is Constant<T>) return Value.Equals(((Constant<T>)obj).Value);
      else return base.Equals0(obj);
    }
    public object BaseValue { get { return Value; } }
    public override bool IsConstant {
      get { return true; }
    }
    public override bool IsObviousConstant {
      get {
        return true;
      }
    }
  }
  partial class NowExpr<T> : Expr<T>, IBindableExpr<T> {
    public readonly Expr<T> Underlying;
    private readonly T CurrentValue0;
    public NowExpr(Expr<T> Underlying) {
      this.Underlying = Underlying;
      this.CurrentValue0 = Underlying.CurrentValue;
    }
    public override T CurrentValue { get { return CurrentValue0; } }

    protected override string ToString1() { return CurrentValue0.ToString(); }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      return txt.DoEval(new Constant<T>(CurrentValue0));
    }
    public bool DelayBindWith(Expr<T> value, List<Action> DoBind) {
      var assign = new NowAssign<Action>();
      if (Underlying.Solve(assign, value)) {
        var assigns = assign.Results.ToArray();
        foreach (var r in assigns) DoBind.Add(r);
        return true;
      } else {
        DoBind = null;
        return false;
      }
    }
    public bool BindWith(Expr<T> value) {
      var assign = new NowAssign<Action>();
      if (Underlying.Solve(assign, value)) {
        foreach (var r in assign.Results) r();
        return true;
      }
      return false;
    }
    public bool TwoWayBindWith(Expr<T> value) {
      return false;
    }
    public bool BindWith(Expr value) { return BindWith((Expr<T>)value); }
  }
  // Allow for fast expression keying.
  public struct ExprKey {
    public Expr Original;
  }
  public class ExprKeys {
    public ExprKey this[Expr e] { get { return new ExprKey() { Original = e }; } }
  }
}
